Chapter 19
Advanced Database Support

by Bill Heyman

In This Chapter

  The COM Approach 686
  OLE DB Consumers Using the ATL Wrapper Classes 687
  Using the ADO C++ Interfaces 708

The COM Approach

Beyond ODBC and ADO, the next step in the evolution of data access requires that the focus be less on databases and more on general datasources. At the same time, the general software development approach requires the packaging of reusable software parts in components. The convergence of these requirements resulted in the development of Microsoft’s strategic software direction, called Universal Data Access (UDA).

A key component of UDA is OLE DB. Based on COM, OLE DB defines a set of interfaces for interacting with general datasources from both the client (consumer) side and the server (provider) side. In addition, Microsoft created a language-independent, higher-level data consumer interface called ActiveX Data Objects (ADO), which is built upon the OLE DB infrastructure. This chapter discusses these new interfaces for data access for Windows-based applications: OLE DB and ADO.

OLE DB

OLE DB is a datasource-independent, COM-based interface for providing and consuming data. It is generic enough to support any datasource that can provide tabular data, yet still powerful enough to enable specific interaction with each datasource to enable it to perform at its optimum.

In the standard client/server world, there are sources of data (commonly called servers) and there are users of data (commonly called clients). In OLE DB, these are called providers and consumers, respectively.

This chapter discusses the development of OLE DB consumers using the ATL template interface provided by Microsoft Visual C++. Please refer to the OLE DB documentation from Microsoft for more information on the creation of OLE DB providers.

ADO

By itself, OLE DB is extremely powerful and very general purpose. However, its interface is not always the easiest way of interacting with datasources in a straightforward, standard manner. Furthermore, OLE DB is not well suited for interfacing to Visual Basic, Java, and other languages.

To meet the requirements for “everyday” datasource development and to be a logical extension of MFC/ODBC and DAO, Microsoft created ActiveX Data Objects (ADO). ADO is implemented using OLE DB provider interfaces. However, its interface allows programmers to interact with data at a higher level than OLE DB provides.

Which One Should I Use?

Certainly, you can use both OLE DB and ADO from Visual C++ code. In fact, because both are COM-based components, there is a variety of mechanisms for interfacing them, from using #import to using the Microsoft-defined C++ interfaces.

If you are looking for a high-performance and more functional interface, select OLE DB. The ATL class wrappers for OLE DB enhance and extend its base functionality, without sacrificing its performance.

If you are looking for a set of high-level objects that can easily be used from a variety of languages and retains some of the look-and-feel of MFC/ODBC and DAO, select ADO.

OLE DB Consumers Using the ATL Wrapper Classes

Although you can access the OLE DB COM objects directly, there is an Active Template Library (ATL) set of classes that encapsulate the COM objects for you. This makes using OLE DB much easier from the C++ environment. To use the ATL OLE DB consumer classes, you must add the following file inclusion line to your program, usually in the stdafx.h file in your project:

#include <atldbcli.h>

Visual Studio provides an ATL Object Wizard that can greatly assist you in creating programs that act as OLE DB consumers. The following steps demonstrate how to generate classes for the Books table in the TechBooks sample database. To add this support to your application, follow these steps:

1.  Load the project to which you would like to add data access using OLE DB into Visual Studio.
2.  From the menu bar, choose Insert, New ATL Object.
3.  If prompted to add ATL support to your MFC project, click Yes. Otherwise, proceed to the next step.
4.  From the ATL Object Wizard dialog, choose the category Data Access and the object Consumer, as shown in Figure 19.1. Click Next.


Figure 19.1  The ATL Object Wizard dialog.

5.  Figure 19.2 shows the ATL Object Wizard Properties dialog. In this dialog, click Select Datasource. This displays the Data Link Properties dialog shown in Figure 19.3.


Figure 19.2  The ATL Object Wizard Properties dialog.


Figure 19.3  The Data Link Properties dialog’s Provider tab.

6.  From the Data Link Properties dialog’s Provider tab, select the datasource provider that is appropriate to your datasource. Because the TechBooks database is an Access database, Microsoft Jet 3.51 OLE DB Provider is selected.
7.  Figure 19.4 shows the Data Link Properties dialog’s Connection tab. Depending on the type of provider selected in the preceding step, the controls on this tab can vary. In the example for a Microsoft Jet OLE DB Provider, you can enter a filename for the database. Click OK.


Figure 19.4  The Data Link Properties dialog’s Connection tab.

8.  Next, the Select Database Table dialog is shown, which allows you to choose one of the datasource’s available tables, queries, or procedures (depending on your datasource). Figure 19.5 shows the selection of the Books table. Click OK.


Figure 19.5  The Select Database Table dialog.

9.  Figure 19.6 shows the updated ATL Object Wizard Properties dialog. Note that in the example, the ATL Object Wizard will generate a data class (CBooks) and an accessor class (CBooksAccessor). If you plan on inserting, updating, and deleting records from this table, check the appropriate check boxes. Next, choose whether you want your generated class to be derived from CCommand (Command) or CTable (Table). Click OK.


Figure 19.6  The ATL Object Wizard Properties dialog with class information.

10.  At this point, your project will contain two new classes: The first is the CAccessor-derived class for your table, and the second is the CCommand- or CTable-derived class for your table. Listing 19.1 shows the CBooksAccessor class and Listing 19.2 shows the CBooks class.

Listing 19.1 The Generated CBooksAccessor Class


class CBooksAccessor
{
public:
   LONG m_BookId;
   LONG m_CategoryId;
   TCHAR m_ISBN[13];
   DATE m_PublicationDate;
   LONG m_PublisherId;
   CURRENCY m_RetailPrice;
   TCHAR m_Title[251];
   LONG m_TopicId;

BEGIN_COLUMN_MAP(CBooksAccessor)
   COLUMN_ENTRY(1, m_BookId)
   COLUMN_ENTRY(2, m_Title)
   COLUMN_ENTRY(3, m_PublisherId)
   COLUMN_ENTRY_TYPE(4, DBTYPE_DATE, m_PublicationDate)
   COLUMN_ENTRY(5, m_ISBN)
   COLUMN_ENTRY_TYPE(6, DBTYPE_CY, m_RetailPrice)
   COLUMN_ENTRY(7, m_CategoryId)
   COLUMN_ENTRY(8, m_TopicId)
END_COLUMN_MAP()

   // You may wish to call this function if you are inserting a
   // record and wish to initialize all the fields, if you are
   // not going to explicitly set all of them.
   void ClearRecord()
   {
      memset(this, 0, sizeof(*this));
   }
};



Listing 19.2 The Generated CBooks Class


class CBooks : public CTable<CAccessor<CBooksAccessor> >
{
public:
   HRESULT Open()
   {
      HRESULT   hr;
      hr = OpenDataSource();
      if (FAILED(hr))
         return hr;

      return OpenRowset();
   }
   HRESULT OpenDataSource()
   {
      HRESULT     hr;
      CDataSource db;
      CDBPropSet  dbinit(DBPROPSET_DBINIT);

      dbinit.AddProperty(DBPROP_AUTH_CACHE_AUTHINFO, true);
      dbinit.AddProperty(DBPROP_AUTH_ENCRYPT_PASSWORD, false);
      dbinit.AddProperty(DBPROP_AUTH_MASK_PASSWORD, false);
      dbinit.AddProperty(DBPROP_AUTH_PASSWORD, OLESTR(“”));
      dbinit.AddProperty(DBPROP_AUTH_PERSIST_ENCRYPTED, false);
      dbinit.AddProperty(DBPROP_AUTH_PERSIST_SENSITIVE_AUTHINFO,
                                                           false);
      dbinit.AddProperty(DBPROP_AUTH_USERID, OLESTR(“Admin”));
      dbinit.AddProperty(DBPROP_INIT_DATASOURCE,
                         OLESTR(“e:\\samples\\TechBooks.mdb”));
      dbinit.AddProperty(DBPROP_INIT_MODE, (long)16);
      dbinit.AddProperty(DBPROP_INIT_PROMPT, (short)4);
      dbinit.AddProperty(DBPROP_INIT_PROVIDERSTRING,
                         OLESTR(“;COUNTRY=0;CP=1252;LANGID=0x0409”));
      dbinit.AddProperty(DBPROP_INIT_LCID, (long)1033);
      hr = db.Open(_T(“Microsoft.Jet.OLEDB.3.51”), &dbinit);
      if (FAILED(hr))
         return hr;

      return m_session.Open(db);
   }
   HRESULT OpenRowset()
   {
      // Set properties for open
      CDBPropSet   propset(DBPROPSET_ROWSET);
      propset.AddProperty(DBPROP_IRowsetChange, true);
      propset.AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE |
                          DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE);

      return CTable<CAccessor<CBooksAccessor> >::Open(m_session, _T(“Books”), &propset);
   }
   CSession   m_session;
};

CDataSource

The CDataSource class contains the information required to open sessions for a datasource. You must create an instance of the CDataSource class before you can open a session (CSession) with that datasource.

Open and Close

The Open method specifies the connection information to the data provider. The Close method releases all the system resources associated with this connection information. These methods are declared as follows:

HRESULT Open(const CLSID& clsid, DBPROPSET* pPropSet = NULL);
HRESULT Open(const CLSID& clsid, LPCTSTR pName = NULL,
             LPCTSTR pUserName = NULL, LPCTSTR pPassword = NULL,
             long nInitMode = 0 );
HRESULT Open(LPCTSTR szProgID, DBPROPSET* pPropSet);
HRESULT Open(LPCTSTR szProgID, LPCTSTR pName = NULL,
             LPCTSTR pUserName = NULL, LPCTSTR pPassword = NULL,
             long nInitMode = 0 );
HRESULT Open(const CEnumerator& enumerator,
             DBPROPSET* pPropSet = NULL);
HRESULT Open(const CEnumerator& enumerator,
             LPCTSTR pName = NULL, LPCTSTR pUserName = NULL,
             LPCTSTR pPassword = NULL, long nInitMode = 0);
HRESULT Open(HWND hWnd = GetActiveWindow(),
             DBPROMPTOPTIONS dwPromptOptions =
                               DBPROMPTOPTIONS_WIZARDSHEET );
void Close();

You can specify the datasource provider that you need to access by using either its class identifier (CLSID) or program identifier string. Alternatively, you can specify a CEnumerator object that has been initialized to point to the datasource to open. The final alternative form (which has HWND and DBPROMPTOPTIONS parameters) allows the users to select a datasource provider from those available on the system.

In addition to the more basic Open method, several other methods allow you to open a datasource. These methods are declared as follows:

HRESULT OpenWithPromptFileName(HWND hWnd = GetActiveWindow(),
             DBPROMPTOPTIONS dwPromptOptions = DBPROMPTOPTIONS_NONE,
             LPCOLESTR szInitialDirectory = NULL);
HRESULT OpenFromFileName(LPCOLESTR szFileName);
HRESULT OpenFromInitializationString(LPCOLESTR szInitializationString);

The OpenWithPromptFileName opens a dialog that allows the user to select a data link file (with .MDL extension) for the data connection to open. If you don’t need to prompt the user for the location of the data link file, call the OpenFromFileName method and specify the filename directly. Finally, the OpenFromInitializationString method allows you to specify the connection string for the datasource to open.

Connection Strings

Like the ODBC connection string described in Chapter 18, the OLE DB connection string is used to specify the exact nature and location of the datasource connection to open. However, the OLE DB connection string has a slightly different syntax and slightly different keywords than its ODBC counterpart.

In general, an OLE DB connection string is a set of semicolon-delimited keyword/value pairs that is of the following form:

keyword1=value1;keyword2=value2

The keywords are often datasource provider-specific, except for the standard Provider keyword. You can use Provider=MSDASQL to specify an ODBC connection string or Provider=Microsoft.Jet.OLEDB.3.5.1 to specify a Microsoft Access database. Note that if no provider keyword is specified, the connection string is assumed to be equal to MSDASQL (ODBC datasource), and the string is equal to the ODBC connection string.

CSession

The CSession class encapsulates an active connection to the datasource. All interactions with the datasource are done through the open session. Additionally, use the CSession class to manage transactions to your datasource via its StartTransaction, Commit, and Abort methods.

Open and Close

Like many of the other Open and Close methods available to Microsoft’s data access classes, the CSession class’s Open and Close methods follow the same paradigm. The Open method allows you to put the CSession object in a usable state and associate it with an active database connection. The Close method releases the resources opened by its Open counterpart. These methods are declared as follows:

HRESULT Open(const CDataSource& ds);
void Close();


NOTE:  

The datasource object (CDataSource) that you specify to the CSession::Open method must be in an Open state or an error will occur.


When your datasource session object is open, you can interact with it using the CTable, CCommand, and CRowset classes.

StartTransaction, Commit, and Abort

You can manage database transactions through the StartTransaction, Commit, and Abort methods. Call StartTransaction to initiate a datasource transaction; when initiated, all database changes are included in the transaction. When you want the changes to be saved to the datasource, call Commit. If you need to roll back the changes, call Abort. These methods are declared as follows:

HRESULT StartTransaction(ISOLEVEL isoLevel =
            ISOLATIONLEVEL_READCOMMITTED, ULONG isoFlags = 0,
            ITransactionOptions* pOtherOptions = NULL,
            ULONG* pulTransactionLevel = NULL ) const;
HRESULT Commit(BOOL bRetaining = FALSE, DWORD grfTC = XACTTC_SYNC,
            DWORD grfRM = 0) const;
HRESULT Abort(BOID* pboidReason = NULL, BOOL bRetaining = FALSE,
            BOOL bAsync = FALSE );



The parameters to StartTransaction allow you to modify the behavior of the transaction that you’re starting. The first parameter, isoLevel, allows you to specify the transaction isolation level. The datasource uses the isolation level to manage the transaction to handle some specific database actions that could occur in the transaction (dirty reads, nonrepeatable reads, and phantoms), a discussion of which is beyond the scope of this book. The second parameter, isoFlags, is reserved and must be equal to zero. The third parameter, pOtherOptions, is typically equal to NULL, but can be the object returned by CSession::GetOptionsObject. Finally, the last parameter, pulTransactionLevel, returns the nesting level of the current transaction; the top-level transaction returns 1. Fortunately, your application can call StartTransaction without parameters and use its default behavior.

The parameters to Commit allow you to spefy the behavior of the datasource commit operation. The first parameter, bRetaining, allows you to specify whether a new transaction is immediately started after the data is committed. The second parameter, grfTC, indicates how quickly the Commit method should return to the caller: immediately (XATTC_ASYNC_PHASEONE), upon completion of phase one (XATTC_SYNC_PHASEONE), or upon completion of a full two-phase commit (XATTC_SYNC_PHASETWO/XATTC_SYNC). The third parameter, qrfRM, must be equal to zero. In the future, it may be used to specify a resource manager for the transaction. Because all the parameters are optional, you can simply call Commit with no parameters to get the most commonly needed behavior.

The parameters to Abort allow you to modify the behavior or the rollback action. The first parameter, pboidReason, is used to specify the unit of work that has been aborted. The second parameter, bRetaining, allows you to specify whether a new transaction is immediately started when the data has been rolled back. Finally, the third parameter, bAsync, allows you to specify whether the call should return immediately or wait until the rollback has occurred. As with the Commit method, all the parameters are optional, and you can simply call Abort with no parameters to get the most likely behavior that your application requires.


Note:  

Some datasource providers allow you to nest transactions. When your datasource supports nested transactions, you can call StartTransaction more than once for an open datasource session. When a nested transaction is opened, the database changes are committed or rolled back for the most recently started transaction. In addition, the transaction is not complete until the number of calls to Abort and Commit equals the number of calls to StartTransaction.


Accessors

Accessors are the glue that binds variables within your application to columns in your datasource. Using accessors, you can obtain and change the data in a datasource. Refer to the section “OLE DB Consumers Using the ATL Wrapper Classes” earlier in this chapter for information on using Visual Studio to associate your classes with a datasource.

All accessors derive from an abstract base class named CAccessorBase. There are four CAccessorBase-derived classes that OLE DB consumers can use to do data binding: CAccessor, CDynamicAccessor, CDynamicParameterAccessor, and CManualAccessor.

CAccessor

The CAccessor template class statically connects data elements within your application to the data values returned from the CRowset class. This template is declared as follows:

template < class T >
class CAccessor : public T, CAccessorBase

The template parameter class, T, is the user-defined class that contains the data elements to which you need to bind the datasource columns. Listing 19.2 shows an example of a generated CBooks class that is bound to the Books table in the sample TechBooks database.

CDynamicAccessor

Use the CDynamicAccessor class when your application does not know the actual structure of the data returned from a rowset. The CDynamicAccessor class allows your application to retrieve the metadata for the rowset, including the number of columns and each column’s type, length, and value.

GetColumnCount

The GetColumnCount returns the number of columns of data available in the current rowset. This method is declared as follows:

ULONG GetColumnCount() const;

Use GetColumnCount to obtain the total number of available columns to iterate through each of the columns in the rowset. Most of the other methods in this class (including GetColumnName, GetColumnType, and GetLength) allow you to specify a zero-based ordinal value to indicate which specific column to interact with.

GetColumnName, GetColumnType, and GetLength

The GetColumnName, GetColumnType, and methods return metadata about a specific column, specifically its name, type, and size, respectively. These methods are declared as follows:

LPOLESTR GetColumnName(ULONG nColumn) const;
bool GetColumnType(ULONG nColumn, DBTYPE* pType) const;
bool GetLength(ULONG nColumn, ULONG* pLength) const;
bool GetLength(TCHAR* pColumnName, ULONG* pLength) const;

The most common column types that are returned by GetColumnType are shown in Table 19.1. Note that the return type includes one of the flags shown in Table 19.1 to indicate that the data is an array, vector, or pointer to the base column type. In addition, Table 19.2 shows the modifiers available to create array, vector, and pointer types.



Table 19.1 Common Column Types Returned from GetColumnType

Type Description

DBTYPE_STR char[]; a null-terminated ANSI string.
DBTYPE_WSTR wchar_t[]; a null-terminated Unicode string.
DBTYPE_BSTR BSTR; a null-terminated character string that includes its length.
DBTYPE_I1 Signed char; a one-byte, signed integer.
DBTYPE_UI1 Unsigned char; a one-byte, unsigned integer.
DBTYPE_I2 short; a two-byte, signed integer.
DBTYPE_UI2 Unsigned short; a two-byte, unsigned integer.
DBTYPE_I4 long; a four-byte, signed integer.
DBTYPE_UI4 Unsigned long; a four-byte, unsigned integer.
DBTYPE_I8 _int64; an eight-byte, signed integer.
DBTYPE_UI8 _int64; an eight-byte, unsigned integer.
DBTYPE_R4 float; a single-precision floating point.
DBTYPE_R8 double; a double-precision floating point.
DBTYPE_CY LARGE_INTEGER; a currency value scaled by 10,000.
DBTYPE_DATE DATE; a double-precision value. The whole part indicates the number of days from the first day of the year 1900, and the fractional part is the part of the day.
DBTYPE_DBDATE DBDATE; year, month, and day.
DBTYPE_DBTIME DBTIME; hour, minute, and second.
DBTYPE_DBTIMESTAMP DBTIMESTAMP; year, month, day, hour, minute, second, and fraction of a second.
DBTYPE_ERROR SCODE; a 32-bit error code.
DBTYPE_BOOL VARIANT_BOOL; a Boolean value in which 0 is FALSE and -1 is TRUE.
DBTYPE_DECIMAL DECIMAL; a value with fixed precision and scale.
DBTYPE_BYTES BYTE[]; a binary array of byte values.

Table 19.2 Column Type Modifiers Returned from GetColumnType

Flag Description

DBTYPE_ARRAY SAFEARRAY *; an array
DBTYPE_BYREF void *; a pointer
DBTYPE_VECTOR DBVECTOR; a pointer to an array with a size

GetValue, and SetValue

The GetValue and SetValue family of methods obtains or changes the value associated with a specified column in the current record in the associated rowset. These methods are declared as follows:

void* GetValue(ULONG nColumn) const;
void* GetValue(TCHAR* pColumnName) const;
template < class ctype >
bool GetValue(ULONG nColumn, ctype* pData) const;
template < class ctype >
bool GetValue(TCHAR *pColumnName, ctype* pData) const;
template < class ctype >
bool SetValue(TCHAR *pColumnName, const ctype& data);
template < class ctype >
bool SetValue(ULONG nColumn, const ctype& data);

Use the template versions of these methods to get or set the value of a column’s data using a specific datatype.

GetStatus and SetStatus

The GetStatus and SetStatus methods allow you to modify a nondata value associated with the data that indicates the validity of the column’s data. These methods are declared as follows:

bool GetStatus( ULONG nColumn, DBSTATUS* pStatus ) const;
bool GetStatus( TCHAR* pColumnName, DBSTATUS* pStatus ) const;
bool SetStatus( ULONG nColumn, DBSTATUS status );
bool SetStatus( TCHAR* pColumnName, DBSTATUS status );

Use these methods when you need to check whether a column’s data is null or set a column’s data as null. Table 19.3 lists the possible DBSTATUS values returned from GetStatus. Table 19.4 lists the possible DBSTATUS values passed to SetStatus.

Table 19.3 Status Values Returned from GetStatus

Type Description

DBSTATUS_S_OK The data is valid.
DBSTATUS_S_ISNULL The data is a null value.
DBSTATUS_S_TRUNCATED The data was truncated.
DBSTATUS_E_BADACCESSOR The data binding was invalid for this column.
DBSTATUS_E_CANTCONVERTVALUE The data conversion failed for this column’s data.
DBSTATUS_E_CANTCREATE A memory or COM object creation error has occurred.
DBSTATUS_E_DATAOVERFLOW The data conversion failed because the data exceeded the bounds of the bound datatype.
DBSTATUS_SIGNMISMATCH An unsigned value was bound to an signed value (or vice versa).
DBSTATUS_UNAVAILABLE The data value was unavailable to the datasource.

Table 19.4 Status Values Set with SetStatus

Type Description

DBSTATUS_S_OK The data is valid.
DBSTATUS_S_ISNULL The data is a null value.
DBSTATUS_S_DEFAULT The datasource should use the default value for the column.
DBSTATUS_S_IGNORE The datasource should skip this column’s data.



CDynamicParameterAccessor

The CDynamicParameterAccessor class extends the CDynamicAccessor interface, allowing your program to get the metadata for the commands. Just as CDynamicAccessor provides information for output columns, the CDynamicParameterAccessor class provides information for parameters, including the number of parameters and each parameter’s type, name, and value.

GetParamCount

The GetParamCount returns the number of parameters associated with the current command. This method is declared as follows:

ULONG GetParamCount() const;

Use GetParamCount to obtain the total number of parameters to iterate through each of the parameters for the command. Most of the other methods in this class (including GetParamName and GetParamType) allow you to specify a zero-based ordinal value to indicate which specific parameter to interact with.

GetParamName and GetParamType

The GetParamName and GetParamType methods return metadata about a specific parameter, specifically its name and type, respectively. These methods are declared as follows:

LPOLESTR GetParamName(ULONG ulParam) const;
bool GetParamType(ULONG ulParam, DBTYPE* pType) const;

The most common parameter types returned by GetParamType are shown in Table 19.1. Note that the return type includes one of the flags shown in Table 19.2 to indicate that the data is an array, vector, or pointer to the base column type.

GetParam and SetParam

The GetParam and SetParam family of methods obtains or changes the value associated with a specified parameter in the associated command. These methods are declared as follows:

void* GetValue(ULONG ulParam) const;
void* GetValue(TCHAR* pParamName) const;
template < class ctype >
bool GetValue(ULONG ulParam, ctype* pData) const;
template < class ctype >
bool GetValue(TCHAR * pParamName, ctype* pData) const;
template < class ctype >
bool SetValue(TCHAR * pParamName, const ctype& data);
template < class ctype >
bool SetValue(ULONG ulParam, const ctype& data);

Use the template versions of these methods to get or set the value of a parameter’s data using a specific datatype.

CManualAccessor

The CManualAccessor class provides a mechanism for binding data buffers to your datasource at a low level. Intended for advanced use, the CManualAccessor class interface requires you to perform your own accessor buffer management.

You must call CreateAccessor or CreateParameterAccessor before calling the respective AddBindEntry or AddParameterEntry methods.

CreateAccessor and CreateParameterAccessor

The CreateAccessor and CreateParameterAccessor methods initialize the CManualAccessor object and prepare it for bind entries to be set in it via the AddBindEntry and AddParameterEntry method calls. The CreateAccessor and CreateParameterAccessor methods are declared as follows:

HRESULT CreateAccessor(int nBindEntries, void* pBuffer,
                       ULONG nBufferSize);
HRESULT CreateParameterAccessor(int nBindEntries,
                       void* pBuffer, ULONG nBufferSize);

Use these methods to specify the number of bind entries that are to be added and the buffer that contains all the bound memory locations.

AddBindEntry and AddParameterEntry

The AddBindEntry and AddParameterEntry methods specify the binding between the datasource column or parameter and the memory in a preallocated buffer. These methods are declared as follows:

void AddBindEntry(ULONG nOrdinal, DBTYPE wType,
                  ULONG nColumnSize,
                  void* pData, void* pLength = NULL,
                  void* pStatus = NULL);
void AddParameterEntry(ULONG nOrdinal, DBTYPE wType,
                  ULONG nColumnSize,
                  void* pData, void* pLength = NULL,
                  void* pStatus = NULL,
                  DBPARAMIO eParamIO = DBPARAMIO_INPUT);

The first parameter, nOrdinal, is equal to the bind entry ordinal to set. The type parameter, wType, can be one of the values shown in Table 19.1. The nColumnSize parameter indicates the number of bytes for e datasource column. The pLength parameter returns the number of bytes for the field in the buffer. The pStatus parameter points to a location in the buffer to be bound to the column’s status values. Finally, the eParamIO parameter specifies whether the parameter is input (DBPARAMIO_INPUT), output (DBPARAMIO_OUTPUT), or both (DBPARAMIO_INPUT | DBPARAMIO_OUTPUT).

Rowsets

Programs send data to and retrieve data from a datasource using a rowset. Very similar to the MFC/ODBC CDBRecordset and DAO CDaoRecordset classes, the CRowset family of classes allows programs to move through a set of records from a datasource.

When using rowsets, you are most likely to use the CRowset class’s interface, which provides basic, single-row retrieval and updating on the datasource. If you’d like to improve the performance of your application because it deals with large amounts of data at a time, use the CBulkRowset class, which is optimized for this situation. Finally, if you need to interact with a rowset as an array of elements (such as a C++ array), use the CArrayRowset class.

CRowset

The CRowset class is the base class for the CBulkRowset and CArrayRowset classes and provides the core rowset functionality for OLE DB. This functionality includes browsing, adding, updating, and deleting datasource rows.

MoveFirst, MoveLast, MoveNext, and MovePrev

The MoveFirst, MoveNext, MovePrev, and MoveLast methods are used to scan through a recordset from first record to last record (or vice versa). These methods are declared as follows:

HRESULT MoveFirst();
HRESULT MoveLast();
HRESULT MoveNext();
HRESULT MoveNext(LONG lSkip, bool bForward);
HRESULT MovePrev();



When a method call to each of these methods is successful, it returns an HRESULT equal to S_OK. Consequently, it is easy to iterate through a set of records by using a while loop as shown in Listing 19.3.

Listing 19.3 Displaying Rows from the Books Table


static void getRecords()
{
   CBooks books;
   if (FAILED(books.Open())) {
      cerr << “Error in books.Open()” << endl;
      return;
   }
   while (books.MoveNext() == S_OK) {
      cout << (const TCHAR *) books.m_ISBN  << _T(“\t”)
           << (const TCHAR *) books.m_Title << endl;
   }
   books.Close();
}

Insert, Update, and Delete

The Insert, Update, and Delete methods provide a mechanism for doing the standard data operations for your datasource. Use the Insert method to create and initialize a new data row. Use the Update method to change the data in the current row. Use the Delete method to remove the current data row from the datasource. These methods are declared as follows:

HRESULT Insert(int nAccessor = 0, bool bGetHRow = false);
HRESULT Update(ULONG* pcRows = NULL, HROW* phRow = NULL,
               DBROWSTATUS* pStatus = NULL);
HRESULT Delete();

The Insert, Update, and Delete methods are demonstrated for the TechBooks sample database in Listings 19.4, 19.5, and 19.6, respectively.


Note:  

If you are using Update, make sure that your accessor does not include any key columns. Otherwise, the call to Update will fail.

You can resolve this problem by creating an accessor that contains only the fields that you need to update and calling SetData to specify the accessor to use—before calling Update.


Listing 19.4 Inserting a New Book Entry Using OLE DB


static void addRecord()
{
   CBooks books;
   if (FAILED(books.Open())) {
      cerr << “Error in books.Open()” << endl;
      return;
   }
   books.ClearRecord();
   books.m_BookId = 123;
   _tcscpy(books.m_ISBN,  “1234567890”);
   _tcscpy(books.m_Title, “MFC Unleashed”);   books.m_RetailPrice.int64 = (49.99) * 10000;
   if (FAILED(books.Insert())) {
      cerr << “Error in books.Insert()” << endl;
   }
   else {
      cout << _T(“added record”) << endl;
   }
   books.Close();
}

Listing 19.5 Updating an Existing Book Entry Using OLE DB


static void editRecord()
{
   CBooks books;
   if (FAILED(books.Open())) {
      cerr << “Error in books.Open()” << endl;
      return;
   }
   while (books.MoveNext() == S_OK) {
      if (_tcscmp(books.m_ISBN, _T(“1234567890”)) == 0) {
         _tcscpy(books.m_ISBN, _T(“0987654321”));
         // set to use accessor #1, which only references
         // the ISBN column—since that’s the only column
         // that we’re updating...
         books.SetData(1);

         books.Update();
         cout << _T(“edited record”) << endl;
      }
   }
   books.Close();
}

Listing 19.6 Deleting a Book Entry Using OLE DB


static void deleteRecord()
{
   CBooks books;
   if (FAILED(books.Open())) {
      cerr << “Error in books.Open()” << endl;
      return;
   }
   while (books.MoveNext() == S_OK) {
      if ((_tcscmp(books.m_ISBN, _T(“1234567890”)) == 0)
          || (_tcscmp(books.m_ISBN, _T(“0987654321”)) == 0)) {
         if (FAILED(books.Delete())) {
            cerr << “Error in books.Delete()” << endl;
            return;
         }
         else {
            cout << _T(“deleted record”) << endl;
         }
      }
   }
   books.Close();
}

Close

The Close method releases all the allocated system resources associated with the open rowset. This method is declared as follows:

void Close();

CBulkRowset

The CBulkRowset class is a CRowset specialization that is optimized for accessing large amounts of data from a remote datasource. This class retrieves data from the datasource in blocks of multiple rows, thus reducing the amount of overall network traffic required to transmit and receive each row of data.

SetRows

Use the SetRows method to specify the number of rows that the CBulkRowset should manage at a time. If you do not call this method, OLE DB uses a default value of 10 rows. This method is declared as follows:

void SetRows(ULONG nRows);

If your application is processing a large amount of data, except for the times that you’ve retrieved a multiple of this row value, you should notice that calls to MoveNext are much faster.

CArrayRowset

The CArrayRowset class template allows your application to interact with a rowset in a random access manner. Consequently, although you can still use the MoveNext and MovePrev methods to move sequentially forward and backward through the rowset, you can use an ordinal value to reference the specific row that you need. This class template is declared as follows:

template < class T, class TRowset = CRowset >
class CArrayRowset : public CVirtualBuffer <T> , public TRowset

Use CArrayRowset when you need optimized random access to a rowset from a datasource.

Operator[]

The only method that CArrayRowset provides to extend the CRowset interface is operator[]. This method is declared as follows:

T& operator[](ULONG nRow);

Accessing Datasource Data

Although CRowset-derived classes allow you to interact with the data retrieved from a datasource, the association of your datasource session with that rowset has not been discussed yet. This section describes the use of CAccessorRowset-based template classes to make this connection.

CAccessorRowset

The CAccessorRowset class is an abstract base class from which the CTable and CCommand template classes derive. The class hierarchy based at this class represents an association between a CAccessor class and a CRowset class. Consequently, it provides a few methods of interest: FreeRecordMemory and Close.

FreeRecordMemory

The FreeRecordMemory method frees all the memory and removes any references asso-ciated with the pointer-based types and objects in the current record. It is declared as follows:

void FreeRecordMemory();

Use FreeRecordMemory before reading each record to release the memory associated with the objects in the previous record.

Close

The Close method closes the associated rowset and releases any system resources associated with that rowset. This method is declared as shown here:

void Close();

CCommand

The CCommand class template allows you to send a command to the datasource. Objects of this class associate an open CSession object with a mechanism for processing the rowset (CRowset) and a mechanism for binding the data to user variables (CAccessor). This class template is declared as shown here:

template <class TAccessor = CNoAccessor, class TRowset = CRowset,
          class TMultiple = CNoMultiple>
class CCommand : public CAccessorRowset<TAccessor, TRowset>
               , public CCommandBase

The TMultiple template parameter can be either CNoMultipleResults (if a single set of results is returned) or CMultipleResults (if multiple result sets are returned).

Open

The Open method executes a command, optionally binding the accessor to the command. The Open method is declared as shown:

HRESULT Open(DBPROPSET *pPropSet = NULL, LONG* pRowsAffected = NULL,
             bool bBind = true );
HRESULT Open(const CSession& session, LPCTSTR szCommand = NULL,
             DBPROPSET *pPropSet = NULL, LONG* pRowsAffected = NULL,
             REFGUID guidCommand = DBGUID_DEFAULT, bool bBind = true);

Create, CreateCommand, and ReleaseCommand

The Create and CreateCommand methods associate a new session with the current command. The CreateCommand method also releases the current command and sets a new command. If you need to release the command without creating a new one, call ReleaseCommand. These methods are declared as follows:

HRESULT Create(const CSession& session, LPCTSTR szCommand,
               REFGUID guidCommand = DBGUID_DEFAULT);
HRESULT CreateCommand(const CSession& session);
HRESULT ReleaseCommand();

Prepare and Unprepare

The Prepare method validates the current command and prepares an execution plan for it at the datasource. The Release method destroys that execution plan. These methods are declared as follows:

HRESULT Prepare(ULONG cExpectedRuns = 0);
HRESULT Unprepare();

GetParameterInfo and SetParameterInfo

Use the GetParameterInfo method to retrieve the parameters to be passed to the current command. Use SetParameterInfo to specify these parameters. These two methods are declared as follows:

HRESULT GetParameterInfo(ULONG* pParams, DBPARAMINFO** ppParamInfo,
                         OLECHAR** ppNamesBuffer );
HRESULT SetParameterInfo(ULONG ulParams, const ULONG* pOrdinals,
                         const DBPARAMBINDINFO* pParamInfo );

GetNextResult

The GetNextResult method processes and returns the next result set for commands that return multiple result sets. This method is declared as shown here:

HRESULT GetNextResult(LONG* pulRowsAffected, bool bBind = true);



CTable

The CTable class template, derived from CAccessorRowset, provides a simple interface for interacting with a datasource that returns a rowset without requiring any parameters. This class template is declared as follows:

template <class TAccessor = CNoAccessor, class TRowset = CRowset >
class Table : public CAccessorRowset <T, TRowset>

This class only provides a single method (excluding those from its base classes) to access a parameterless rowset: Open.

Open

The Open method opens a simple, parameterless rowset for a datasource. Its two forms are declared as shown here:

HRESULT Open(const CSession& session, LPCTSTR szTableName,
             DBPROPSET* pPropSet = NULL);
HRESULT Open( const CSession& session, DBID& dbid,
             DBPROPSET* pPropSet = NULL);

Using the ADO C++ Interfaces

The ADO interfaces are a set of COM components. Consequently, you can use them in a variety of ways from Visual C++. This chapter discusses the use of ADO interfaces using the C++ interfaces supplied by Microsoft with Visual C++.

To use ADO in Visual C++ can sometimes be a bit more of a challenge than using it with some of the scripting languages (such as VBScript and JavaScript). However, with the ADO 2 extensions supplied with Microsoft Visual Studio 6, much of the pain is taken out of the process. Using the TechBooks database example, this section steps you through the key issues that you’ll encounter when using ADO in your application.

First, to use the ADO C++ interfaces, make sure that the following file inclusion lines are in your application (most likely in stdafx.h):

#include <adoid.h>   // class and interface identifiers
#include <adoint.h>  // ADO interface
#include <icrsint.h> // ADO 2.0 data binding extensions

These files declare the ADO class and interface identifiers, the interface itself, and the extensions for data binding, respectively.

Next, you need to derive a class that contains your bound data members from the CADORecordBinding class. Within this class, add data members to which you’d like to bind the data returned from the datasource provider. In addition, for each bound data member, create a status field (of type ULONG) that will be used for reporting bind errors to your application. Finally, create an ADO binding section within your class that maps the ordinal column numbers in the database table to your class members. Listing 19.7 shows the CBooks class, which is associated with the Books table in the TechBooks sample database.

Listing 19.7 The CBooks Class Using ADO Data Binding


class CBooks : public CADORecordBinding {
public:
   LONG     m_BookId;
   LONG     m_CategoryId;
   TCHAR    m_ISBN[13];
   DATE     m_PublicationDate;
   LONG     m_PublisherId;
   CURRENCY m_RetailPrice;
   TCHAR    m_Title[251];
   LONG     m_TopicId;
   ULONG    m_stsBookId;
   ULONG    m_stsCategoryId;
   ULONG    m_stsISBN;
   ULONG    m_stsPublicationDate;
   ULONG    m_stsPublisherId;
   ULONG    m_stsRetailPrice;
   ULONG    m_stsTitle;
   ULONG    m_stsTopicId;

BEGIN_ADO_BINDING(CBooks)
   ADO_NUMERIC_ENTRY(1, adInteger, m_BookId, 10, 0, m_stsBookId, false)
   ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_Title, sizeof(m_Title),    m_stsTitle, true)
   ADO_NUMERIC_ENTRY(3, adInteger, m_PublisherId, 10, 0, m_stsPublisherId,    true)
   ADO_FIXED_LENGTH_ENTRY(4, adDate, m_PublicationDate,    m_stsPublicationDate, true)
   ADO_VARIABLE_LENGTH_ENTRY2(5, adChar, m_ISBN, sizeof(m_ISBN),    m_stsISBN, true)
   ADO_FIXED_LENGTH_ENTRY(6, adCurrency, m_RetailPrice, m_stsRetailPrice,    true)
   ADO_NUMERIC_ENTRY(7, adInteger, m_CategoryId, 10, 0, m_stsCategoryId,    true)
   ADO_NUMERIC_ENTRY(8, adInteger, m_TopicId, 10, 0, m_stsTopicId, true)
END_ADO_BINDING()

   // You may wish to call this function if you are inserting a record and    //wish to initialize all the fields, if you are not going to    //explicitly set all of them.
   void ClearRecord()
   {
      memset(this, 0, sizeof(*this));
   }
};

Within the CBooks example, note that various macros are used to specify how each data member needs to be bound to the column. When you have a class derived from CADORecordBinding, follow the steps after Listing 19.8 to create and use an ADORecordset object within your application. Listing 19.8 illustrates these steps.

Listing 19.8 Initializing and Using ADO


int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
   int nRetCode = 0;
   // initialize COM (step 1)
   CoInitialize(NULL);

   HRESULT hr;
   ADORecordset *rstADO;
   // create an instance of the CADORecordset class (step 2)
   hr = CoCreateInstance(
                         //”{00000535-0000-0010-8000-00AA006D2EA4}”,
                         CLSID_CADORecordset,
                         NULL,
                         CLSCTX_INPROC_SERVER,
                         IID_IADORecordset,
                         (LPVOID *) &rstADO);

   if (FAILED(hr)) {
      cerr << “Unable to create ADORecordset object.” << endl;
      CoUninitialize();
      return 1;
   }
   // obtain the IADORecordBinding interface (step 3)
   IADORecordBinding *rstADOBind;
   hr = rstADO->QueryInterface(__uuidof(IADORecordBinding), (LPVOID *)    &rstADOBind);
   if (FAILED(hr)) {
      cerr << “Unable to obtain IADORecordBinding interface.” << endl;
      CoUninitialize();
      return 1;
   }

   // open the recordset (step 4)
   CString table(“Books”);
   CString connect;
   connect += “DRIVER={Microsoft Access Driver (*.mdb)};”;
   connect += “UID=Admin;”;
   connect += “DBQ=”;
   connect += argv[1];

   hr = rstADO->Open(COleVariant(table),
                     COleVariant(connect),
                     adOpenKeyset, adLockOptimistic,
                     adCmdTable);
   if (FAILED(hr)) {
      cerr << “Unable to open ADORecordset object.” << endl;
      CoUninitialize();
      return 1;
   }
   // bind your class to the recordset (step 5)
   CBooks books;

   rstADOBind->BindToRecordset(&books);

   // add code here to browse, insert, update, or delete (step 6)
   // close the recordset and release the record bind (step 7)
   rstADO->Close();
   rstADOBind->Release();

   // clean up COM (step 8)
   CoUninitialize();

   return nRetCode;
}

1.  Call CoInitialize to initialize COM.
2.  Create an instance of the ADORecordset class using CoCreateInstance.
3.  Obtain a pointer to the IADORecordBinding interface from this recordset object. (Note: If this call fails, it is probably because either you do not have ADO 2 installed or the CLSID and IID that you are using refer to ADO 1 objects. Refer to the following sidebar for more information on how to resolve this problem.)



Errors Obtaining IADORecordBinding

If you get an error when trying to get a pointer to the IADORecordBinding interface, it is likely that your application is using ADO 1, not ADO 2.

To determine whether this is the case, run your program in the debugger in Visual Studio. When the call to CoCreateInstance is made, the output window should indicate that msado15.dll is loaded. (For whatever reason, msado15.dll contains the ADO 2 interface.)

If the debugger reports that msado10.dll was loaded, your application is passing the incorrect CLSID to CoCreateInstance to construct its interface.

To correct this situation, you need to build a source file that contains the correct, newer class and interface identifiers. Listing 19.13 shows such a file. Simply compile this file (outside your current project—perhaps even in its own project) and link its object file into your application.

You might note that Microsoft ships a library file named adoid.lib with Visual Studio 6. However—and unfortunately—it contains the class and interface identifiers for ADO 1.

4.  Open the created recordset object using its Open method.
5.  Bind your CADORecordBinding-derived class to the record binding interface using its Bind method.
6.  To browse the recordset’s records, see the code in Listing 19.9. To add a new record, see Listing 19.10. To change a record, see Listing 19.11. To delete a record, see Listing 19.12.
7.  When finished, close the recordset (using its Close method) and release the record binding interface (using its Release method).
8.  Call CoUninitialize to release the COM resources owned by the application.

Listing 19.9 Doing a Forward Scan of a Table’s Records Using ADO


static void getRecords(ADORecordset *rst, CBooks& books)
{
   VARIANT_BOOL isEOF;

   while (true) {
      rst->get_EOF(&isEOF);
      if (isEOF == VARIANT_TRUE) {
         break;
      }
      cout << (const TCHAR *) books.m_ISBN  << _T(“\t”)
           << (const TCHAR *) books.m_Title << endl;
      rst->MoveNext();
   }
}

Listing 19.10 Adding a New Book Entry Using ADO


static void addRecord(ADORecordset *rst,
                      IADORecordBinding *rstADOBind,
                      CBooks& books)
{
   books.ClearRecord();
   books.m_BookId = 123;
   books.m_CategoryId = 1;
   _tcscpy(books.m_ISBN, _T(“1234567890”));
   books.m_PublicationDate = 39000.0;
   books.m_PublisherId = 1;
   books.m_RetailPrice.int64 = (49.99) * 10000;
   _tcscpy(books.m_Title, _T(“MFC Unleashed”));
   books.m_TopicId = 1;

   rstADOBind->AddNew(&books);
   cout << _T(“added record”) << endl;
}

Listing 19.11 Updating an Existing Book Entry Using ADO


static void editRecord(ADORecordset *rst,
                       IADORecordBinding *rstADOBind,
                       CBooks& books)
{
   VARIANT_BOOL isEOF;
   while (true) {
      rst->get_EOF(&isEOF);

      if (isEOF == VARIANT_TRUE) {
         break;
      }
      if (_tcscmp(books.m_ISBN, _T(“1234567890”)) == 0) {
         _tcscpy(books.m_ISBN, _T(“0987654321”));
         rstADOBind->Update(&books);
         cout << _T(“edited record”) << endl;
      }
      rst->MoveNext();
   }
}

Listing 19.12 Deleting a Book Entry Using ADO


static void deleteRecord(ADORecordset *rst, CBooks& books)
{
   VARIANT_BOOL isEOF;
   while (true) {
      rst->get_EOF(&isEOF);
      if (isEOF == VARIANT_TRUE) {
         break;
      }
      if ((_tcscmp(books.m_ISBN, _T(“1234567890”)) == 0)
          || (_tcscmp(books.m_ISBN, _T(“0987654321”)) == 0)) {
         rst->Delete(adAffectCurrent);

         cout << _T(“deleted record”) << endl;
      }
      rst->MoveNext();
   }
}

Listing 19.13 The Source File for Creating the Most Recent Class and Interface Identifiers for ADO


// Yes, you only need the three header file inclusions shown below.
// However, you cannot include adoid.h at any point before this set
// of file inclusion statements.
#include <objbase.h>
#include <initguid.h>
#include <adoid.h>

ADOConnection

The ADOConnection class manages information required for connecting to a datasource provider and allows you to open a live connection to it via the Open and Close methods. In addition, you can create and manage transactions: BeginTrans, CommitTrans, and RollbackTrans. Finally, you can execute commands against the datasource via the Execute method.

Open and Close

The Open and Close methods create and destroy a network connection to a datasource, respectively. These methods are declared as shown here:

HRESULT Open(BSTR ConnectionString, BSTR UserID, BSTR Password,
             long Options = 0);
HRESULT Close();

The connection string must be in OLE DB format, as described in the OLE DB CDataSource::Open method earlier in this chapter. The Options parameter allows you to indicate whether to perform the connection synchronously (adConnectUnspecified) or asynchronously (adAsyncConnect).

ConnectionString, Provider DefaultDatabase, ConnectionTimeout, and Mode

The ConnectionString, Provider, DefaultDatabase, ConnectionTimeout, and Mode properties are used to indicate how to connect to the datasource provider and in what manner. The methods associated with these properties are declared as follows:

HRESULT get_ConnectionString(BSTR *pbstr);
HRESULT put_ConnectionString(BSTR bstr);
HRESULT get_Provider(BSTR *pbstr);
HRESULT put_Provider(BSTR bstr);
HRESULT get_DefaultDatabase(BSTR *pbstr);
HRESULT put_DefaultDatabase(BSTR bstr);
HRESULT get_ConnectionTimeout(LONG *plTimeout);
HRESULT put_ConnectionTimeout(LONG lTimeout);
HRESULT get_Mode(ConnectModeEnum *plMode);
HRESULT put_Mode(ConnectModeEnum lMode);

The timeout values for the ConnectionTimeout property are in seconds. The Mode property controls access to the database; its values are listed in Table 19.5.

Table 19.5 Values Allowed for the Mode Property

Value Description

adModeUnknown No permissions (default).
adModeRead Read-only permissions.
adModeWrite Write-only permissions.
adModeReadWrite Read and write permissions.
adModeShareDenyRead Deny other readers.
adModeShareDenyWrite Deny other writers.
adModeShareExclusive Deny other readers and writers.
adModeShareDenyNone Allow others to access as readers and/or writers.



BeginTrans, CommitTrans, RollbackTrans, and IsolationLevel

The BeginTrans, CommitTrans, and RollbackTrans methods manage the transaction state for the current open connection. The IsolationLevel property informs the datasource about the characteristics of the transaction. The methods for managing transactions are shown here:

HRESULT BeginTrans(long *TransactionLevel);
HRESULT CommitTrans();
HRESULT RollbackTrans();
HRESULT get_IsolationLevel(IsolationLevelEnum *Level);
HRESULT put_IsolationLevel(IsolationLevelEnum Level);

Call BeginTrans to start a transaction, which causes all changes to the database through this open connection to be managed as a single entity. Upon completion of the database changes, call CommitTrans to save the changes to the database. If you need to undo the changes belonging to this transaction, call RollbackTrans.

The IsolationLevel property can have one of the values specified in Table 19.6.

Table 19.6 Values Allowed for the IsolationLevel Property

Value Description

adXactUnspecified Unknown.
adXactChaos Overwrite of higher isolation-level transactions is not allowed (default).
adXactReadUncommitted Transaction can view uncommitted changes in other transactions.
adXactBrowse Same as adXactReadUncommitted.
adXactCursorStability Transaction can view only committed changes in other transactions (default).
adXactReadCommitted Same as adXactCursorStability.
adXactRepeatableRead Requeries of the same data sets can return different results—but only for committed data.
adXactSerializable Transaction is isolated from other transactions.
adXactIsolated Same as adXactSerializable.

Execute and CommandTimeout

Use the Execute method to send datasource-specific text to a datasource provider. In cases where your application is interacting with a relational database, this is likely to be a SQL statement. The CommandTimeout property allows you to specify the amount of time (in seconds) to wait for your commands to be executed before giving up waiting on results. The methods used for executing commands are shown here:

HRESULT Execute(BSTR CommandText, VARIANT *RecordsAffected,
                long Options, ADORecordset **ppiRset);
HRESULT get_CommandTimeout(LONG *plTimeout);
HRESULT put_CommandTimeout(LONG lTimeout);

The options allowed by the Execute method are shown in Table 19.7. Use the ADOCommand object if you need to pass parameters to the command—to invoke a stored procedure on a SQL Server that requires parameters, for example.

Table 19.7 Option Values for the Execute Method

Value Description

adCmdText The command is a text string (probably SQL).
adCmdTable The command is the name of a table, for which ADO should create a SQL query to return all its rows.
adCmdTableDirect The command is the name of a table, for which the datasource provider should return all its rows.
adCmdStoredProc The command is the name of a stored procedure.
adCmdUnknown The type of the command is unknown.
adAsyncExecute The command should execute asynchronously.
adAsyncFetch The rows beyond the number indicated by the recordset’s CacheSize property should be retrieved asynchronously.
adAsyncFetchNoBlocking The data retrieval request never blocks the calling thread.

Errors

The Errors collection contains a list of ADOError objects that are created when an error occurs. This collection is accessed using the following method:

HRESULT get_Errors(ADOErrors **ppvObject);

Sometimes when a datasource provider error occurs, it can cause a domino effect of errors through the nested ADO classes. The collection of errors allows you to trace back to the original locus of the error.

Properties

The Properties collection is a set of datasource provider-dependent keys and values that modify the way the datasource handles this ADOConnection object. Use the following method to gain access to this collection:

HRESULT get_Properties(ADOProperties **ppvObject);

ADORecordset

The ADORecordset class encapsulates the set of results returned from a datasource provider. Using this class, you can browse the returned records and, optionally, insert a new record or update or delete an existing record.

Open and Close

Use the Open method to create a recordset by sending a command to a datasource over a specific connection. Use the Close method to release all the resources associated with the open recordset. These methods are declared as follows:

HRESULT Open(VARIANT Source, VARIANT ActiveConnection,
             CursorTypeEnum CursorType, LockTypeEnum LockType,
             LONG Options);
HRESULT Close();

The Source variant parameter can contain either a pointer to an ADOCommand object or a command text string. The ActiveConnection variant parameter can contain either a pointer to an ADOConnection object or a connection string. The CursorType parameter can contain one of the values listed in Table 19.8. The LockType parameter can contain one of the values listed in Table 19.9. The Options parameter can contain one of the values listed in Table 19.10.



Table 19.8 Cursor Types for the Open Method

Value Description

adOpenForwardOnly The recordset supports forward browsing only (default).
adOpenKeyset The recordset uses a keyset cursor.
adOpenDynamic The recordset uses a dynamic cursor.
adOpenStatic The recordset uses a static cursor.

Table 19.9 Lock Types for the Open Method

Value Description

adLockReadOnly The data in the recordset is read-only (default).
adLockPessimistic Use pessimistic locking, which locks the record upon edit.
adLockOptimistic Use optimistic locking, which locks the record only at update.
adLockBatchOptimistic Use optimistic locking when BatchUpdate is called.

TD VALIGN="TOP" ALIGN="LEFT">The data retrieval request never blocks the calling thread.
Table 19.10 Option Values for the Open Method

Value Description

adCmdText The command is a text string (probably SQL).
adCmdTable The command is the name of a table, for which ADO should create a SQL query to return all its rows.
adCmdTableDirect The command is the name of a table, for which the datasource provider should return all its rows.
adCmdStoredProc The command is the name of a stored procedure.
adCmdUnknown The type of the command is unknown.
adCmdFile The recordset should be restored from the file named by the command.
adAsyncExecute The command should execute asynchronously.
adAsyncFetch The rows beyond the number indicated by the recordset’s CacheSize property should be retrieved asynchronously.
adAsyncFetchNoBlocking

Source

The Source property contains the value that was supplied to the Open method via its Source variant parameter. This variant could contain a pointer to an ADOCommand object or a text string containing the table name, stored procedure name, or SQL statement used to create the recordset. The methods to access this property are declared as follows:

HRESULT putref_Source(IDispatch *pcmd);
HRESULT put_Source(BSTR bstrConn);
HRESULT get_Source(VARIANT *pvSource);

MoveFirst, MoveLast, MoveNext, MovePrevious, BOF, and EOF

The MoveFirst, MoveLast, MoveNext, and MovePrevious methods are used to scan through a recordset from first record to last record (or vice versa). Additionally, use the BOF and EOF properties to determine whether the recordset is currently at the beginning or end. These methods are declared as follows:

HRESULT MoveFirst();
HRESULT MoveLast();
HRESULT MoveNext();
HRESULT MovePrevious();
HRESULT get_BOF(VARIANT_BOOL *pb);
HRESULT get_EOF(VARIANT_BOOL *pb);

To use these methods to scan and list a set of database records, call these methods as shown in Listing 19.9. This code simply creates a while loop that checks the EOF property and calls MoveNext to advance to the next record. The data class members contain the data associated with the current record.


Note:  

If you make changes to the recordset’s data class members while scanning the table, your changes will be lost. Use the Update methods to change the record’s data and reflect the data in the recordset.


AddNew, Update, and Delete

The AddNew, Update, and Delete methods provide a mechanism for doing the standard data operations for your datasource. Use the AddNew method to create and initialize a new data row. Use the Update method to change the data in the current row. Use the Delete method to remove the current data row from the datasource. These methods are declared as follows:

HRESULT AddNew(VARIANT FieldList, VARIANT Values);
HRESULT Update(VARIANT Fields, VARIANT Values);
HRESULT Delete(AffectEnum AffectRecords);

The Delete method’s AffectRecords parameter accepts the values displayed in Table 19.11.

Table 19.11 Values for the Delete Method

Value Description

adAffectCurrent Delete the current record (default).
adAffectGroup Delete all records satisfying the Filter property.
adAffectAll Delete all records.
adAffectAllChapters Delete all chapter records.



CacheSize

Use the CacheSize property to specify the number of rows that the recordset should cache. You can get and set this property using the following methods:

HRESULT get_CacheSize(long *pl);
HRESULT put_CacheSize(long CacheSize);

By default, the CacheSize property is set to one. You can modify this property to improve overall network traffic and performance.

ActiveConnection

Use the ActiveConnection property to get or set the connection to which this ADORecordset object belongs. The methods to manipulate this property are declared as follows:

HRESULT get_ActiveConnection(_ADOConnection **ppvObject);
HRESULT putref_ActiveConnection(_ADOConnection *pCon);
HRESULT put_ActiveConnection(VARIANT vConn);

Fields

The Fields collection contains the set of data elements for a single record. You can retrieve the values returned from the datasource provider by iterating through this collection of ADOField objects. The method for obtaining access to this collection is declared as follows:

HRESULT get_Fields(ADOFields **ppvObject);

Properties

The Properties collection is a set of datasource provider-dependent keys and values that modify the way the datasource handles this ADORecordset object. Use the following method to gain access to this collection:

HRESULT get_Properties(ADOProperties **ppvObject);

ADOCommand

The ADOCommand class encapsulates a command string that is sent to the database. Typically a SQL statement, this command could be the name of a table, stored procedure, or a datasource-dependent string.

CommandType and CommandText

Use the CommandType and CommandText properties to specify datasource-specific text for a datasource provider. In cases where your application is interacting with a relational database, this is likely to be a SQL statement. The methods used for executing commands are shown here:

HRESULT put_CommandType(CommandTypeEnum lCmdType);
HRESULT get_CommandType(CommandTypeEnum *plCmdType);
HRESULT get_CommandText(BSTR *pbstr);
HRESULT put_CommandText(BSTR bstr);
HRESULT get_CommandTimeout(LONG *plTimeout);
HRESULT put_CommandTimeout(LONG lTimeout);

The types allowed by the CommandType property are shown in Table 19.7.

Execute and CommandTimeout

Use the Execute method to send datasource-specific text to a datasource provider. In cases where your application is interacting with a relational database, this is likely to be a SQL statement. The CommandTimeout property allows you to specify the amount of time (in seconds) to wait for your commands to be executed before giving up waiting on results. The methods used for executing commands are shown here:

HRESULT Execute(BSTR CommandText, VARIANT *RecordsAffected,
                VARIANT *Parameters,
                long Options, ADORecordset **ppiRset);
HRESULT get_CommandTimeout(LONG *plTimeout);
HRESULT put_CommandTimeout(LONG lTimeout);

The options allowed by the Execute method are shown in Table 19.7. Use the ADOCommand object if you need to pass parameters to the command—to invoke a stored procedure on a SQL Server that requires parameters, for example.

CreateParameter and Parameters

The CreateParameter method adds a new parameter to the Parameters collection for the current command. Use the following methods to interact with command parameters:

HRESULT CreateParameter(BSTR Name, DataTypeEnum Type,
                        ParameterDirectionEnum Direction,
                        long Size, VARIANT Value,
                        ADOParameter **ppiprm);
HRESULT get_Parameters(ADOParameters **ppvObject);

The values for the Type parameter are shown in Table 19.12. Flags that can modify the Type parameter (via a logical OR operation) are listed in Table 19.13. The Direction parameter designates whether the parameter is input or output (or both) and can have one of the values listed in Table 19.14.

Table 19.12 Common ADO Datatypes

Type Description

adChar char[]; a null-terminated ANSI string.
adWChar wchar_t[]; a null-terminated Unicode string.
adBSTR BSTR; a null-terminated character string that includes its length.
adTinyInt signed char; a one-byte, signed integer.
adUnsignedTinyInt unsigned char; a one-byte, unsigned integer.
adSmallInt short; a two-byte, signed integer.
adUnsignedSmallInt unsigned short; a two-byte, unsigned integer.
adInteger long; a four-byte, signed integer.
adUnsignedInt unsigned long; a four-byte, unsigned integer.
adBigInt _int64; an eight-byte, signed integer.
adUnsignedBigInt _int64; an eight-byte, unsigned integer.
adSingle float; a single-precision floating point.
adDouble double; a double-precision floating point.
adCurrency LARGE_INTEGER; a currency value scaled by 10,000.
adDate DATE; a double-precision value. The whole part indicates the number of days from the first day of the year 1900, and the fractional part is the part of the day.
adDBDate DBDATE; year, month, and day.
adDBTime DBTIME; hour, minute, and second.
adDBTimeStamp DBTIMESTAMP; year, month, day, hour, minute, second, and fraction of a second.
adError SCODE; a 32-bit error code.
adBoolean VARIANT_BOOL; a Boolean value in which 0 is FALSE and -1 is TRUE.
adDecimal DECIMAL; a value with fixed precision and scale.
adBinary BYTE[]; a binary array of byte values.



Table 19.13 ADO Datatype Modifiers

Flag Description

adArray SAFEARRAY *; an array
adByRef void *; a pointer
adVector DBVECTOR; a pointer to an array with a size

Table 19.14 Parameter Direction Values

Value Description

adParamUnknown Unknown direction.
adParamInput Input parameter (default).
adParamOutput Output parameter.
adParamInputOutput Input and output parameter.
adParamReturnValue Return value.

Prepared

Use the Prepared property to specify whether the datasource provider should precompile the command. If the datasource supports preparing commands, this can improve the performance of commonly executed queries. The methods to get and set this property are declared as follows:

HRESULT get_Prepared(VARIANT_BOOL *pfPrepared);
HRESULT put_Prepared(VARIANT_BOOL fPrepared);

ActiveConnection

Use the ActiveConnection property to get or set the connection to which this ADOCommand object belongs. The methods to manipulate this property are declared as follows:

HRESULT get_ActiveConnection(_ADOConnection **ppvObject);
HRESULT putref_ActiveConnection(_ADOConnection *pCon);
HRESULT put_ActiveConnection(VARIANT vConn);

Properties

The Properties collection is a set of datasource provider-dependent keys and values that modify the way the datasource handles this ADOCommand object. Use the following method to gain access to this collection:

HRESULT get_Properties(ADOProperties **ppvObject);

ADOField

The ADOField class represents an output datatype from the data provider. Because ADOField is a member of the ADORecordset’s Fields collection, you can retrieve the field’s metadata and associated value from objects of this class.

Name, Type, DefinedSize, and ActualSize

The Name, Type, DefinedSize, and ActualSize properties contain information about the structure of the data itself. These properties can be get and set using the following methods:

HRESULT get_Name(BSTR *pbstr);
HRESULT get_Type(DataTypeEnum *pDataType);
HRESULT get_DefinedSize(long *pl);
HRESULT get_ActualSize(long *pl);

The values for the Type property are shown in Table 19.12. Flags that can modify the Type property (via a logical OR operation) are listed in Table 19.13.

Value and Attributes

Use the Value and Attributes properties to specify the data associated with the specific fields whose value is being set. The methods for manipulating the Value and Attributes properties are shown here:

HRESULT get_Value(VARIANT *pval);
HRESULT put_Value(VARIANT val);
HRESULT get_Attributes(long *plAttributes);
HRESULT put_Attributes(long lAttributes);

Properties

The Properties collection is a set of datasource provider-dependent keys and values that modify the way the datasource handles this ADOField object. Use the following method to gain access to this collection:

HRESULT get_Properties(ADOProperties **ppvObject);

ADOProperty

The ADOProperty class encapsulates a single data provider-dependent value that is passed to the data provider via many of the ADO objects. The whole list of parameters that are to be sent to and retrieved from the datasource is in the Fields collection of the ADOConnection, ADOCommand, ADORecordset, and ADOField classes. The ADOProperty class contains the metadata for the property (name and type), as well as its value.

Name and Type

The Name and Type properties contain information about the property itself. These properties can be manipulated using the following methods:

HRESULT get_Name(BSTR *pbstr);
HRESULT get_Type(DataTypeEnum *ptype);

The values for the Type property are shown in Table 19.12. Flags that can modify the Type property (via a logical OR operation) are listed in Table 19.13.

Value and Attributes

Use the Value and Attributes properties to specify the data associated with the specific properties whose value is being set. The datasource provider defines the values and attributes associated with each property. The methods for manipulating the Value and Attributes properties are shown here:

HRESULT get_Value(VARIANT *pval);
HRESULT put_Value(VARIANT val);
HRESULT get_Attributes(long *plAttributes);
HRESULT put_Attributes(long lAttributes);

ADOParameter

The ADOParameter class encapsulates a single parameter that is to be passed to a stored procedure or query via an ADOCommand object. The whole list of parameters that are to be sent to and retrieved from the datasource is in the ADOCommand class’s Parameters collection. The ADOParameter class contains the metadata for the parameter (name, type, and size), as well as its value.

Name, Type, and Size

The Name, Type, and Size properties contain information about the parameter itself. These properties can be manipulated using the following methods:

HRESULT get_Name(BSTR *pbstr);
HRESULT put_Name(BSTR bstr);
HRESULT get_Type(DataTypeEnum *psDataType);
HRESULT put_Type(DataTypeEnum sDataType);
HRESULT put_Size(long l);
HRESULT get_Size(long *pl);

The values for the Type property are shown in Table 19.12. Flags that can modify the Type property (via a logical OR operation) are listed in Table 19.13.

Value

The Value property contains the parameter’s value in the form specified by its Type property. This property can be accessed using the following methods:

HRESULT get_Value(VARIANT *pvar);
HRESULT put_Value(VARIANT val);

ADOError

The ADOError class contains detailed information about the ADO error that has occurred. You can access ADOError objects via the Errors collection in the ADOConnection object.

Description, Number, NativeError, and Source

The Description, Number, NativeError, and Source properties provide the detailed error information that your application can use to perform appropriate error handling—including the display of an error to the user. The methods to interact with these properties are shown here:

HRESULT get_Number(long_*pl);
HRESULT get_Description(BSTR *pbstr);
HRESULT get_NativeError(long *pl);
HRESULT get_Source(BSTR *pbstr);


Tip:  

Although you can use the NativeError property to get an error code from the data provider, make sure that your connection is to the data provider that you expect before handling this error code.


Summary

This chapter discusses the next step in the evolution of Microsoft data access technologies. This step unites component-based software development and datasource-independent programming interfaces. The set of Microsoft technologies that achieves these goals is called Universal Data Access (UDA).

One of the key technologies within this set is called OLE DB, which defines standard COM components for the development of data providers and data consumers. A higher-level, language-independent interface called ActiveX Data Objects (ADO) is implemented using the OLE DB consumer interfaces.